package sg.edu.nus.iss.cerberus.engine;

/**
 * The concrete builder class, main class, for creating of JUnit 4 Test Class. 
 * This class calls TestEngine object for creation of test case methods.
 * 
 * @author Priyanka
 * @author Lin
 * 
 * */

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.internal.junit.JUnitCorePlugin;
import org.eclipse.jdt.internal.junit.Messages;
import org.eclipse.jdt.internal.junit.util.JUnitStubUtility;
import org.eclipse.jdt.internal.junit.util.JUnitStubUtility.GenStubSettings;
import org.eclipse.jdt.internal.junit.wizards.WizardMessages;
import org.eclipse.jdt.ui.CodeGeneration;

import sg.edu.nus.iss.cerberus.model.*;
import sg.edu.nus.iss.cerberus.util.CerberusConstants;

public class JUnit4TestClassBuilder extends TestClassBuilder {

	/**
	 * A method for creating of SetUp Test Case.
	 * 
	 * @param testGenData TestGenearationData object
	 * 
	 * */
    @Override
    public void generateSetUpMethod(TestGenerationData testGenData) {
    	try {
            createSetupStubs(CerberusConstants.SETUP, false,"org.junit.Before", testGenData);
        } catch (CoreException e) {
            e.printStackTrace();
        }
    }

    /**
	 * A method for creating of TearDown Test Case.
	 * 
	 * * @param testGenData TestGenearationData object
	 * 
	 * */
    @Override
    public void generateTearDownMethod(TestGenerationData testGenData) {
        try {
        	createSetupStubs(CerberusConstants.TEARDOWN, false,"org.junit.After", testGenData);
        } catch (CoreException e) {
         e.printStackTrace();
        } 

    }

    /**
	 * A method for creating of SetUpBefore Test Case.
	 * 
	 * * @param testGenData TestGenearationData object
	 * 
	 * */
    @Override
    public void generateSetUpBeforeClassMethod(TestGenerationData testGenData) {
        try {
            createSetupStubs(CerberusConstants.SETUP_CLASS, true,"org.junit.BeforeClass", testGenData);
        } catch (CoreException e) {
             e.printStackTrace();
        }

    }

    /**
	 * A method for creating of TearDownAfter Test Case.
	 * 
	 * * @param testGenData TestGenearationData object
	 * 
	 * */
    @Override
    public void generateTearDownAfterClassMethod(TestGenerationData testGenData) {
        try {
        	createSetupStubs(CerberusConstants.TEARDOWN_CLASS, true,"org.junit.AfterClass", testGenData);
        } catch (CoreException e) {
            e.printStackTrace();
        } 

    }

    /**
	 * A method for creating of test cases for user defined methods.
	 * 
	 * * @param testGenData TestGenearationData object
	 * 
	 * */
    @Override
    public void generateUserMethods(TestGenerationData testGenData) {
        try {
            createTestMethodStubs(testGenData);
        } catch (CoreException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private IMethod findInHierarchy(IType type, String methodName)
            throws JavaModelException {
        ITypeHierarchy typeHierarchy = null;
        IType[] superTypes = null;
        if (type.exists()) {
            typeHierarchy = type.newSupertypeHierarchy(null);
            superTypes = typeHierarchy.getAllSuperclasses(type);
            for (int i = 0; i < superTypes.length; i++) {
                if (superTypes[i].exists()) {
                    IMethod testMethod = superTypes[i].getMethod(methodName,
                            new String[] {});
                    if (testMethod.exists()) {
                        return testMethod;
                    }
                }
            }
        }
        return null;
    }

    private String getLineDelimiter(IType classToTest, IPackageFragment pac)
            throws JavaModelException {
        if (classToTest != null && classToTest.exists()
                && classToTest.getCompilationUnit() != null)
            return classToTest.getCompilationUnit()
                    .findRecommendedLineSeparator();

        return pac.findRecommendedLineSeparator();
    }

    private void createTestMethodStubs(TestGenerationData testGenData)
            throws CoreException, Exception {
        
        if (testGenData.getTestClass() == null) {
            IMethod[] methods = testGenData.getClassUnderTest().getMethods();
            if (methods.length == 0)
                return;
            
            IMethod[] allMethodsArray = testGenData.getType().getMethods();
            List<IMethod> allMethods = new ArrayList<IMethod>();
            allMethods.addAll(Arrays.asList(allMethodsArray));
            List<IMethod> overloadedMethods = getOverloadedMethods(allMethods);

            List<String> names = new ArrayList<String>();
            for (int i = 0; i < methods.length; i++) {
                IMethod method = methods[i];
                String elementName = method.getElementName();
                StringBuffer name = new StringBuffer(CerberusConstants.PREFIX).append(Character.toUpperCase(elementName.charAt(0))).append(elementName.substring(1));
                StringBuffer buffer = new StringBuffer();

                final boolean contains = overloadedMethods.contains(method);
                if (contains)
                    appendParameterNamesToMethodName(name,
                            method.getParameterTypes());

                replaceIllegalCharacters(name);
              
                String testName = name.toString();
                if (names.contains(testName)) {
                    int suffix = 1;
                    while (names.contains(testName + Integer.toString(suffix)))
                        suffix++;
                    name.append(Integer.toString(suffix));
                }
                testName = name.toString();
                names.add(testName);

                buffer.append('@').append(testGenData.getImportsManager().addImport(JUnitCorePlugin.JUNIT4_ANNOTATION_NAME)).append(getLineDelimiter(testGenData.getClassUnderTest(),testGenData.getPackageFragment()));

                buffer.append("public ");//$NON-NLS-1$ 
                buffer.append("void ");//$NON-NLS-1$ 
                buffer.append(testName);
                buffer.append("()");//$NON-NLS-1$ 

                ArrayList<String> params = new ArrayList<String>();
                String[] paramsName = method.getParameterNames();
                String[] paramsType = method.getParameterTypes();

                for (int index = 0; index < method.getNumberOfParameters(); index++) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(Signature.toString(paramsType[index])).append(" ").append(paramsName[index]);
                    params.add(sb.toString());
                }

                appendTestMethodBody(testGenData, buffer, elementName, params,Signature.toString(method.getSignature()));

                testGenData.getType().createMethod(buffer.toString(), null,
                        false, null);
            }
        } else {
            try {
              
                StringBuffer buffer = new StringBuffer();
                buffer.append('@').append("Test");
                                                  
                buffer.append("\r\n public ");//$NON-NLS-1$ 
                buffer.append("void ");//$NON-NLS-1$ 
                List<String> testClassMethodNames = new ArrayList<String>(testGenData.getTestClass().getMethodHash().keySet());
                
                //name of the test method created
                StringBuffer name = new StringBuffer(CerberusConstants.PREFIX).append(Character.toUpperCase(testGenData.getMethodTestData().get(0).getName().charAt(0))).append(testGenData.getMethodTestData().get(0).getName().substring(1));
                String testName = name.toString();
                if (testClassMethodNames.contains(testName)) {
                  int suffix = 1;
                  while (testClassMethodNames.contains(testName + Integer.toString(suffix)))
                      suffix++;
                  name.append(Integer.toString(suffix));
                }
                
                //narrowing which source method user has selected
                Iterator<String> itr=testGenData.getCut().getMethodHash().keySet().iterator();
                ArrayList<CMemberMethod> selectedMethod=null;
                String temp=null;
                while(itr.hasNext())
                {
                	temp=itr.next();
                	if(temp.equals(testGenData.getMethodTestData().get(0).getName()))
                		selectedMethod=testGenData.getCut().getMethod(temp);
                }
                
                buffer.append(name);
                buffer.append("()");//$NON-NLS-1$ 
               
                if(selectedMethod.size()>0)
                {
                	CMemberMethod exactMethod=getTheExactMethod(testGenData,selectedMethod);
                	if(exactMethod!=null)
                	appendOneTestMethodBody(testGenData, buffer, testGenData.getMethodTestData().get(0).getName(), exactMethod.getParams()
                    		,exactMethod.getSignature());
                	else
                		System.out.println("ERROR : the user selected method not found in the source class");
                }else{ 
                	appendOneTestMethodBody(testGenData, buffer, testGenData.getMethodTestData().get(0).getName(), selectedMethod.get(0).getParams()
                		,selectedMethod.get(0).getSignature());
                }
                testGenData.getType().createMethod(buffer.toString(), null,true, null);
            } catch (Exception e) {
            e.printStackTrace();
            }
        }
    }

    
    
    private CMemberMethod getTheExactMethod(TestGenerationData testGenData,ArrayList<CMemberMethod> selectedMethod)
    {
    	CMemberMethod exactMethod=null;
    	int i=0;
    	while(i<selectedMethod.size())
    	{
    	ArrayList<String> paramfromMethodTestData=new ArrayList<String>();
    	ArrayList<String> paramfromCMemberMethod=new ArrayList<String>();
    	
    	paramfromMethodTestData=testGenData.getMethodTestData().get(0).getSignature();
    	paramfromCMemberMethod=selectedMethod.get(i).getParams();
    	if(paramfromCMemberMethod!=null &&paramfromMethodTestData!=null &&
    			paramfromCMemberMethod.size()>=1  && paramfromMethodTestData.size()>=1	){
    	Iterator itr2=paramfromCMemberMethod.iterator();
    	int j=0;
    	boolean flag=false;
    	while(itr2.hasNext())
    	{
    		String str=itr2.next().toString().trim();
    		if(str.indexOf(paramfromMethodTestData.get(j).toString().trim())>-1)
    			flag=true;
    		else
    			flag=false;
    		j++;
    	}
    	if(flag)
    		exactMethod=selectedMethod.get(i);
    	               		
    		i++;
    	}
    	else
    		i++;
    	}
    	return exactMethod;
    	
    }
    
    private List<IMethod> getOverloadedMethods(List<IMethod> allMethods) {
        List<IMethod> overloadedMethods = new ArrayList<IMethod>();
        for (int i = 0; i < allMethods.size(); i++) {
            IMethod current = allMethods.get(i);
            String currentName = current.getElementName();
            boolean currentAdded = false;
            for (ListIterator<IMethod> iter = allMethods.listIterator(i + 1); iter
                    .hasNext();) {
                IMethod iterMethod = iter.next();
                if (iterMethod.getElementName().equals(currentName)) {
                    // method is overloaded
                    if (!currentAdded) {
                        overloadedMethods.add(current);
                        currentAdded = true;
                    }
                    overloadedMethods.add(iterMethod);
                    iter.remove();
                }
            }
        }
        return overloadedMethods;
    }
    
    
    private void appendOneTestMethodBody(TestGenerationData testGenData,StringBuffer buffer, String methodName,ArrayList<String> params,
			String methodSignature	) throws CoreException, Exception {
				final String delimiter = "\r\n";
				buffer.append('{').append(delimiter);
				String todoTask = ""; //$NON-NLS-1$
				
				String todoTaskTag = JUnitStubUtility.getTodoTaskTag(testGenData.getType().getCompilationUnit().getJavaProject());
				if (todoTaskTag != null) {
				todoTask = " // " + todoTaskTag; //$NON-NLS-1$
				}
				
				// call the user defined method body
				TestEngine te = new TestEngine(testGenData.getCut());
				MethodTestData tempmethod = testGenData.getMethodTestData(methodName);
				if (tempmethod == null)
				te.createMethodBody(null, buffer, delimiter, methodName, params,methodSignature,testGenData);
				else
				te.createMethodBody(tempmethod.getAssertCheck(), buffer, delimiter,methodName, params, methodSignature,testGenData);
				
				String message = WizardMessages.NewTestCaseWizardPageOne_not_yet_implemented_string;
				buffer.append(Messages.format("fail(\"{0}\");", message)).append(todoTask).append(delimiter); //$NON-NLS-1$
				
				buffer.append('}').append(delimiter);
}
    
    
    private void appendTestMethodBody(TestGenerationData testGenData,StringBuffer buffer, String methodName,ArrayList<String> params,
    									String methodSignature	) throws CoreException, Exception {
        final String delimiter = getLineDelimiter(testGenData.getClassUnderTest(), testGenData.getPackageFragment());
        buffer.append('{').append(delimiter);
        String todoTask = ""; //$NON-NLS-1$
        
       
        String todoTaskTag = JUnitStubUtility.getTodoTaskTag(testGenData.getType().getCompilationUnit().getJavaProject());
        if (todoTaskTag != null) {
            todoTask = " // " + todoTaskTag; //$NON-NLS-1$
        }
      
        // call the user defined method body
        TestEngine te = new TestEngine(testGenData.getCut());
  
        MethodTestData tempmethod = null;        
        if(params.size()!=0)
        	tempmethod= testGenData.getMethodTestData(methodName,params);
		else
			tempmethod= testGenData.getMethodTestData(methodName);
        
        if (tempmethod == null)
            te.createMethodBody(null, buffer, delimiter, methodName, params,methodSignature,testGenData);
        else
            te.createMethodBody(tempmethod.getAssertCheck(), buffer, delimiter,methodName, params, methodSignature,testGenData);

        String message = WizardMessages.NewTestCaseWizardPageOne_not_yet_implemented_string;
        buffer.append(Messages.format("fail(\"{0}\");", message)).append(todoTask).append(delimiter); //$NON-NLS-1$

        buffer.append('}').append(delimiter);
    }

    private void appendParameterNamesToMethodName(StringBuffer buffer,String[] parameters) {
        for (int i = 0; i < parameters.length; i++) {
            final StringBuffer buf = new StringBuffer(Signature.getSimpleName(Signature.toString(Signature.getElementType(parameters[i]))));
            final char character = buf.charAt(0);
            if (buf.length() > 0 && !Character.isUpperCase(character))
                buf.setCharAt(0, Character.toUpperCase(character));
            buffer.append(buf.toString());
            for (int j = 0, arrayCount = Signature.getArrayCount(parameters[i]); j < arrayCount; j++) {
                buffer.append("Array"); //$NON-NLS-1$
            }
        }
    }

    private void replaceIllegalCharacters(StringBuffer buffer) {
        char character = 0;
        for (int index = buffer.length() - 1; index >= 0; index--) {
            character = buffer.charAt(index);
            if (Character.isWhitespace(character))
                buffer.deleteCharAt(index);
            else if (character == '<')
                buffer.replace(index, index + 1, CerberusConstants.OF_TAG);
            else if (character == '?')
                buffer.replace(index, index + 1,
                        CerberusConstants.QUESTION_MARK_TAG);
            else if (!Character.isJavaIdentifierPart(character))
                buffer.deleteCharAt(index);
        }
    }

    private void createSetupStubs(String methodName, boolean isStatic,String annotationType, TestGenerationData testGenData)
            throws CoreException {
        if (testGenData.getFixtureData() != null) {
            if (testGenData.getFixtureData().isAssertExist(methodName)) {
                String content = null;
                IMethod methodTemplate = findInHierarchy(testGenData.getType(),methodName);
                String annotation = null;
                annotation = '@' + testGenData.getImportsManager().addImport(annotationType);

                GenStubSettings settings = JUnitStubUtility.getCodeGenerationSettings(testGenData.getType().getJavaProject());
                settings.createComments = testGenData.getIsAdComment();

                if (methodTemplate != null) {
                    settings.callSuper = true;
                    settings.methodOverwrites = true;
                    content = JUnitStubUtility.genStub(testGenData.getType().getCompilationUnit(), testGenData.getTypeName(), methodTemplate, settings, annotation, testGenData.getImportsManager());
                } else {
                    final String delimiter = getLineDelimiter(
                            testGenData.getClassUnderTest(),
                            testGenData.getPackageFragment());
                    StringBuffer buffer = new StringBuffer();
                    if (settings.createComments) {
                        String[] excSignature = { Signature
                                .createTypeSignature(
                                        "java.lang.Exception", true) }; //$NON-NLS-1$
                        String comment = CodeGeneration.getMethodComment(testGenData.getType().getCompilationUnit(), testGenData.getType().getElementName(),
                        				methodName, new String[0], excSignature,Signature.SIG_VOID, null, delimiter);
                        if (comment != null) {
                            buffer.append(comment);
                        }
                    }
                    if (annotation != null) {
                        buffer.append(annotation).append(delimiter);
                    }
                    buffer.append("public ");

                    if (isStatic) {
                        buffer.append("static "); //$NON-NLS-1$
                    }
                    buffer.append("void "); //$NON-NLS-1$
                    buffer.append(methodName);
                    buffer.append("throws "); //$NON-NLS-1$
                    buffer.append(testGenData.getImportsManager().addImport("java.lang.Exception")); //$NON-NLS-1$
                    buffer.append(" {}"); //$NON-NLS-1$
                    buffer.append(delimiter);
                    content = buffer.toString();
                }
                testGenData.getType().createMethod(content, null, false, null);
            }
        }
    }
}